package com.rpay.common.util.http;


import com.rpay.common.util.LoggerUtil;
import com.rpay.common.util.http.builder.HttpClientxBuilder;
import com.rpay.common.util.http.builder.HttpExecutorBuilder;
import com.rpay.common.util.http.exception.HttpProcessException;
import com.rpay.common.util.http.listener.AbstractEventListener;
import com.rpay.common.util.http.listener.EventObject;
import com.rpay.common.util.http.listener.EventType;
import com.rpay.common.util.http.listener.strategy.ListenerStrategy;
import com.rpay.common.util.http.request.HttpConfig;
import com.rpay.common.util.http.request.HttpMethod;
import com.rpay.common.util.http.response.HttpReturn;
import com.rpay.common.util.http.util.Constants;
import com.rpay.common.util.http.util.ParamUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;

/**
 * Title：
 * Description
 *
 * @author fxg06 on 2018/5/2
 * @version 1.0
 */
public class HttpUtil {
    /**
     * 默认采用的http协议的HttpClient对象
     */
    private static HttpClient client4HTTP;

    /**
     * 默认采用的https协议的HttpClient对象
     */
    private static HttpClient client4HTTPS;

    /**
     * 监听器
     */
    private static Map<EventType, AbstractEventListener> listenerMap = new ConcurrentHashMap<>();


    private HttpUtil() {}

    static{
        try {
            client4HTTP = HttpClientxBuilder.custom().build();
            client4HTTPS = HttpClientxBuilder.custom().ssl().build();
        } catch (HttpProcessException e) {
            LoggerUtil.error("创建https协议的HttpClient对象出错：{}", e);
        }
    }

    public static void addListener(EventType type, AbstractEventListener listener) {
        listenerMap.put(type, listener);
    }

    public static void clearListeners() {listenerMap.clear();}

    public static void removeListener(EventType key) {listenerMap.remove(key);}

    /**
     * 以Get方式，请求资源或服务
     *
     * @param config		请求参数配置
     * @return
     * @throws HttpProcessException
     */
    public static String get(HttpConfig config) throws HttpProcessException {
        return send(config.method(HttpMethod.GET));
    }

    /**
     * 异步以Get方式，请求资源或服务
     * @param config
     * @return
     * @throws HttpProcessException
     */
    public static Future<String> futureGET(HttpConfig config) throws HttpProcessException {
        // 线程变量传递
        String url = config.url();
        OutputStream outputStream = config.out();
        return HttpExecutorBuilder.custom().submit(() -> {
            try {
                return get(config.url(url).out(outputStream));
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        });
    }

    /**
     * 异步以Get方式，请求资源或服务
     * @param config
     * @return
     * @throws HttpProcessException
     */
    public static void asyncGET(HttpConfig config) throws HttpProcessException {
        // 线程变量传递
        String url = config.url();
        OutputStream outputStream = config.out();
        HttpExecutorBuilder.custom().execute(() -> {
            try {
                get(config.url(url).out(outputStream));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 以Post方式，请求资源或服务
     *
     * @param config		请求参数配置
     * @return
     * @throws HttpProcessException
     */
    public static String post(HttpConfig config) throws HttpProcessException {
        return send(config.method(HttpMethod.POST));
    }

    /**
     * 异步以Get方式，请求资源或服务
     * @param config
     * @return Future
     * @throws HttpProcessException
     */
    public static Future<String> asyncPost(HttpConfig config) throws HttpProcessException {
        return HttpExecutorBuilder.custom().submit(() -> {
            try {
                return post(config);
            } catch (HttpProcessException e) {
                e.printStackTrace();
            }
            return null;
        });
    }

    /**
     * 以Put方式，请求资源或服务
     *
     * @param config		请求参数配置
     * @return
     * @throws HttpProcessException
     */
    public static String put(HttpConfig config) throws HttpProcessException {
        return send(config.method(HttpMethod.PUT));
    }

    /**
     * 以Delete方式，请求资源或服务
     *
     * @param config		请求参数配置
     * @return
     * @throws HttpProcessException
     */
    public static String delete(HttpConfig config) throws HttpProcessException {
        return send(config.method(HttpMethod.DELETE));
    }

    /**
     * 以Head方式，请求资源或服务
     *
     * @param config		请求参数配置
     * @return
     * @throws HttpProcessException
     */
    public static String head(HttpConfig config) throws HttpProcessException {
        return send(config.method(HttpMethod.HEAD));
    }

    /**
     * 下载文件
     *
     * @param config		请求参数配置
     * @return						返回处理结果
     * @throws HttpProcessException
     */
    public static OutputStream down(HttpConfig config) throws HttpProcessException {
        HttpReturn httpReturn = null;
        try {
            httpReturn = execute(config.method(HttpMethod.GET));
            return fmt2Stream(httpReturn.getResponse(), config.out());

        }catch (Exception e) {
            LoggerUtil.error("请求异常",e);
        } finally {
            if (httpReturn != null) {
                close(httpReturn.getResponse());
            }
        }
        return null;
    }

    /**
     * 查看资源链接情况，返回状态码
     *
     * @param config		请求参数配置
     * @return				返回处理结果
     * @throws HttpProcessException
     */
    public static int status(HttpConfig config) throws HttpProcessException {
        HttpReturn httpReturn = null;
        try {
            httpReturn = execute(config);
            // 关闭数据流
            EntityUtils.consume(httpReturn.getResponse().getEntity());
            return httpReturn.getCode();
        }catch (Exception e) {
            LoggerUtil.error("请求异常",e);
        } finally {
            if (httpReturn != null) {
                close(httpReturn.getResponse());
            }
        }
        return -1;
    }

    /**
     * 执行请求（无监听器）
     * @param config config
     * @return
     * @throws HttpProcessException
     */
    public static HttpReturn exec(HttpConfig config) throws HttpProcessException {
        // 执行请求
        HttpReturn httpReturn = null;
        try {
            // 执行请求
            httpReturn = execute(config);
            // 解析返回值
            String result = fmt2String(httpReturn.getResponse(), config.outEncode());
            httpReturn.setResult(result);
        } catch (Exception e) {
            e.printStackTrace();
            throw new HttpProcessException("请求异常",e);
        } finally {
            if (httpReturn != null) {
                close(httpReturn.getResponse());
            }
        }
        return httpReturn;
    }

    /**
     * 请求资源或服务
     *
     * @param config		请求参数配置
     * @return
     * @throws HttpProcessException
     */
    private static String send(HttpConfig config) throws HttpProcessException {
        // 监听器执行开始
        URI uri = assembleURI(config);
        notifyListener(EventType.START,listenerMap.get(EventType.START), config,assembleURI(config));
        // 解析返回值
        HttpReturn httpReturn = exec(config);
        // 执行结束
        notifyListener(EventType.FINISH,listenerMap.get(EventType.FINISH), httpReturn,uri);
        return httpReturn.getResult();
    }

    /**
     * 请求资源或服务
     *
     * @param config		请求
     * @return				返回HttpReturn对象
     * @throws HttpProcessException
     */
    private static HttpReturn execute(HttpConfig config) throws HttpProcessException {
        HttpReturn httpReturn;
        try {
            // 组装请求
            HttpRequestBase httpRequest = assembleHttpRequest(config);
            HttpResponse resp = (config.context()==null)?config.client().execute(httpRequest) : config.client().execute(httpRequest, config.context()) ;
            // 设置返回结果
            httpReturn = assembleHttpReturn(resp);
            httpReturn.setHttpConfig(config);
            // http status 判断
          /*  if (httpReturn.getCode() != HttpStatus.SC_OK) {
                URI uri = httpRequest.getURI();
                logger.error("response status not support, httpStatus={}, url={}", httpReturn.getStatus(), uri.toString());
                throw new HttpProcessException("请求结果异常,code:"+httpReturn.getStatus()+",url:"+uri.toString());
            }*/
        } catch (IOException e) {
            throw new HttpProcessException("请求异常",e);
        }
        return httpReturn;

    }

    /**
     * 请求资源或服务
     *
     * @param config		请求参数配置
     * @return				返回HttpResponse对象
     * @throws HttpProcessException
     */
    private static HttpRequestBase assembleHttpRequest(HttpConfig config) throws HttpProcessException {
        // 获取请求链接
        create(config);
        HttpRequestBase request;
        URI uri;
        try {
            //创建请求对象
            request = getRequest(config.url(), config);
            //设置header信息
            request.setHeaders(config.headers());
            uri = request.getURI();
            //判断是否支持设置entity(仅HttpPost、HttpPut、HttpPatch支持)
            if(HttpEntityEnclosingRequestBase.class.isAssignableFrom(request.getClass())){
                List<NameValuePair> nvps = new ArrayList<>();
                //装填参数
                HttpEntity entity = ParamUtil.map2HttpEntity(nvps, config.formMap(),config.body(), config.inEncode());
                //设置参数到请求对象中
                ((HttpEntityEnclosingRequestBase)request).setEntity(entity);
                LoggerUtil.info("请求地址："+uri.toString());
                if(nvps.size()>0){
                    LoggerUtil.info("请求参数："+nvps.toString());
                }
            } else {
                LoggerUtil.info("请求地址："+uri.toString());
            }

        } catch (IOException e) {
            throw new HttpProcessException("请求异常",e);
        }
        return request;
    }

    /**
     * 判定是否开启连接池、及url是http还是https <br>
     * 		如果已开启连接池，则自动调用build方法，从连接池中获取client对象<br>
     * 		否则，直接返回相应的默认client对象<br>
     *
     * @param config		请求参数配置
     * @throws HttpProcessException
     */
    private static void create(HttpConfig config) throws HttpProcessException  {
        // 如果为空，设为默认client对象
        if(config.client()==null){
            String url = config.url();
            if (url!=null ) {
                if(url.toLowerCase().startsWith(Constants.PROTOCOL_HTTPS)){
                    config.client(client4HTTPS);
                } else{
                    config.client(client4HTTP);
                }
            } else  {
                throw new HttpProcessException("未设置请求地址");
            }

        }
    }

    private static HttpReturn assembleHttpReturn(HttpResponse resp) {
        HttpReturn httpReturn = new HttpReturn();
        if (resp != null) {
            httpReturn.setStatus(resp.getStatusLine().toString());
            httpReturn.setCode(resp.getStatusLine().getStatusCode());
            // 获取所有response的header信息
            httpReturn.setHeaders(resp.getAllHeaders());
            httpReturn.setResponse(resp);
        }
        return httpReturn;
    }

    /**
     * 转化为字符串
     * @param httpResponse 响应对象
     * @param encoding	编码
     * @return
     * @throws HttpProcessException
     */
    private static String fmt2String(HttpResponse httpResponse, String encoding) throws HttpProcessException {
        String body;
        try {
            if (httpResponse.getEntity() != null) {
                // 按指定编码转换结果实体为String类型
                body = EntityUtils.toString(httpResponse.getEntity(), encoding);
                LoggerUtil.info(body);
            }else{//有可能是head请求
                body =httpResponse.getStatusLine().toString();
            }
            EntityUtils.consume(httpResponse.getEntity());
        } catch (IOException e) {
            throw new HttpProcessException(e);
        }
        return body;
    }

    /**
     * 转化为流
     *
     * @param httpResponse			响应对象
     * @param out				输出流
     * @return
     * @throws HttpProcessException
     */
    private static OutputStream fmt2Stream(HttpResponse httpResponse, OutputStream out) throws HttpProcessException {
        try {
            httpResponse.getEntity().writeTo(out);
            // 关闭数据流
            EntityUtils.consume(httpResponse.getEntity());
        } catch (IOException e) {
            throw new HttpProcessException(e);
        }
        return out;
    }

    /**
     * 根据请求方法名，获取request对象
     *
     * @param url					资源地址
     * @param config			请求方式
     * @return
     */
    private static HttpRequestBase getRequest(String url, HttpConfig config) {
        HttpRequestBase request = null;
        HttpMethod method = config.method();
        try {
            // 支持rest风格url
            Map<String,String> restMap = config.rest();
            if (restMap!=null && !restMap.isEmpty()) {
                url = ParamUtil.format(url,restMap);
            }
            // 支持get风格url
            Map<String,String> getMap = config.getMap();
            if (getMap!=null && !getMap.isEmpty()) {
                url = ParamUtil.buildParas(url,config.getMap());
            }
            switch (method.getCode()) {
                // HttpGet
                case 0:
                    //装填参数
                    request = new HttpGet(url);
                    break;
                // HttpPost
                case 1:
                    request = new HttpPost(url);
                    break;
                // HttpHead
                case 2:
                    request = new HttpHead(url);
                    break;
                // HttpPut
                case 3:
                    request = new HttpPut(url);
                    break;
                // HttpDelete
                case 4:
                    request = new HttpDelete(url);
                    break;
                default:
                    request = new HttpPost(url);
                    break;
            }
        }  catch (URISyntaxException e) {
            LoggerUtil.error("url 解析出错",e);
        }
        return request;
    }

    /**
     * 尝试关闭response
     *
     * @param resp				HttpResponse对象
     */
    private static void close(HttpResponse resp) {
        try {
            if(resp == null) {
                return;
            }
            // 如果CloseableHttpResponse 是resp的父类，则支持关闭
            if(CloseableHttpResponse.class.isAssignableFrom(resp.getClass())){
                ((CloseableHttpResponse)resp).close();
            }
        } catch (IOException e) {
            LoggerUtil.error("关闭response出错：{}", e);
        }
    }

    /**
     * 执行监听器
     * @param type 监听器
     * @param listener 监听器
     * @param source 监听器数据对象
     * @param uri 访问地址
     */
    private static void notifyListener(EventType type, AbstractEventListener listener, Object source, URI uri) {
        // 判断是否执行
        if (listener!=null) {
            ListenerStrategy listenerStrategy = listener.getSource();
            if (listenerStrategy!=null) {
                if(listenerStrategy.handle(uri)) {
                    listener.handle(new EventObject(type, source));
                }
            }
        }
    }

    private static URI assembleURI(HttpConfig config) throws HttpProcessException {
        try {
            String url = config.url();
            // 支持rest风格url
            Map<String,String> restMap = config.rest();
            if (restMap!=null && !restMap.isEmpty()) {
                url = ParamUtil.format(url,restMap);
            }
            return new URI(url);
        } catch (URISyntaxException e) {
            throw new HttpProcessException("url解析错误",e);
        }
    }
}
